package api

import (
	"io"
	"io/ioutil"
	"log"
	"os"
	"strconv"
	"strings"

	"github.com/kataras/iris/v12"
	"github.com/klauspost/reedsolomon"
)

func Encode(ctx iris.Context) {
	// 1 提取文件数据和元数据
	log.Println("Erasure Setting: ", K, M, L)
	file, fileHeader, err := ctx.FormFile("file")
	if err != nil {
		log.Println("ctx.FormFile Error: ", err)
	}
	fileName := fileHeader.Filename
	fileSize := uint64(fileHeader.Size)
	log.Println("FileInfo: ", fileName, fileSize)
	// 2 执行编码操作
	// 声明编码器实例
	RSEncoder, err := reedsolomon.NewStream(K, M)
	if err != nil {
		log.Println("reedsolomon.NewStream Error: ", err)
	}
	// 声明块句柄序列
	chunkHandles := make([]*os.File, K+M)
	var chunkPaths []string
	for index := range chunkHandles {
		index_str := strconv.Itoa(index)
		chunkPath := CHUNKS_STORE_PATH + fileName + "-" + index_str
		chunkPaths = append(chunkPaths, chunkPath)
		chunkHandles[index], err = os.Create(chunkPath)
		if err != nil {
			log.Println("os.Create Error: ", err)
		}
	}
	// 声明数据块写实体序列，作为 split 操作的输出
	dataWriter := make([]io.Writer, K)
	for i := range dataWriter {
		dataWriter[i] = chunkHandles[i]
	}
	// 实施数据块分割操作
	RSEncoder.Split(file, dataWriter, int64(fileSize))

	// 声明数据块读实体序列，作为 rs-encode 操作的输入
	dataBufferReaders := make([]io.Reader, K)
	for i := range dataWriter {
		chunkHandles[i].Close()
		chunkHandle, err := os.Open(chunkHandles[i].Name())
		if err != nil {
			log.Println("callEncoder() => Open Data Chunk Error: ", err)
		}
		dataBufferReaders[i] = chunkHandle
		defer chunkHandle.Close()
	}
	// 声明校验块写实体序列，作为 encode 操作的输出
	pariWriter := make([]io.Writer, M)
	for i := range pariWriter {
		pariWriter[i] = chunkHandles[K+i]
		defer chunkHandles[K+i].Close()
	}
	// 实施 RS 全局编码
	RSEncoder.Encode(dataBufferReaders, pariWriter)
	// 实施 XOR 局部编码
	chunkInfoTemplate, err := os.Stat(chunkPaths[0])
	if err != nil {
		log.Println("os.Stat Error: ", err)
	}
	xorEncode(chunkPaths[0:K], uint64(chunkInfoTemplate.Size()))
	for i := K + M; i < K+M+L; i++ {
		chunkPath := CHUNKS_STORE_PATH + fileName + "-" + strconv.Itoa(i)
		chunkPaths = append(chunkPaths, chunkPath)
	}
	var byteArr [][]byte = make([][]byte, K+M+L)
	for i := range chunkPaths {
		chunkInfo, err := os.Stat(chunkPaths[i])
		if err != nil {
			log.Println("os.Stat Error: ", err)
		}
		chunkHandle, err := os.Open(chunkPaths[i])
		if err != nil {
			log.Println("Read Chunk Bytes Error: ", err)
		} else {
			var b []byte = make([]byte, chunkInfo.Size())
			chunkHandle.Read(b)
			byteArr[i] = b
		}
	}
	// 3 返回响应
	// golang json Marshal默认对[]byte类型进行base64编码处理
	ctx.JSON(byteArr)
}

func Decode(ctx iris.Context) {
	// 1 提取请求中的文件名、文件大小
	filename := ctx.Params().Get("name")
	fileSizeStr := ctx.Params().Get("size")
	fileSize, err := strconv.ParseInt(fileSizeStr, 10, 64)
	if err != nil {
		log.Println("strconv.ParseInt Error: ", err)
	}
	// 2 在本地 /cache 目录下查找文件（如果 /cache 目录下有文件缓存，说明是上一次读请求留下的）
	cachePath := CACHE_STORE_PATH + filename
	var cacheFile *os.File
	_, err = os.Stat(cachePath)
	if err == nil {
		// 如果命中缓存，直接返回
		ctx.SendFile(cachePath, filename)
		return
	} else {
		// 否则在缓存目录下重新创建文件
		cacheFile, err = os.OpenFile(cachePath, os.O_CREATE|os.O_WRONLY, 0644)
		if err != nil {
			log.Println("os.OpenFile Error: ", err)
		}
		defer cacheFile.Close()
	}
	// 从请求中提取 MultipartForm
	maxSize := ctx.Application().ConfigurationReadOnly().GetPostMaxMemory()
	err = ctx.Request().ParseMultipartForm(maxSize >> 2)
	if err != nil {
		log.Println("ParseMultipartForm Error: ", err)
	}
	form := ctx.Request().MultipartForm
	chunks := form.File["files"]
	for _, c := range chunks {
		src, err := c.Open()
		if err != nil {
			log.Println("c.Open Error: ", err)
		}

		defer src.Close()
		out, err := os.OpenFile(CHUNKS_STORE_PATH+c.Filename, os.O_WRONLY|os.O_CREATE, os.FileMode(0666))
		if err != nil {
			log.Println("os.OpenFile Error: ", err)
		}
		defer out.Close()
		io.Copy(out, src)
	}

	// 3 读本地 /chunks 目录下的编码块，之后执行拼接或修复
	// 加载并检查数据块
	dataChunkPaths := make([]string, K)
	// 循环检查数据块失效情况，如果检测到失效情况，确定恢复方案
	var lostChunksIndex []int
	var lostLocalChunksMap = make(map[int]int)
	var repairSchema = "local"
	chunkSize := int64(0)
	for i := range dataChunkPaths {
		// 加载目标块（数据块）
		dataChunkPaths[i] = CHUNKS_STORE_PATH + filename + "-" + strconv.Itoa(i)
		chunkInfo, err := os.Stat(dataChunkPaths[i])
		if err != nil {
			// 获取文件状态操作出错 => 数据块已失效
			// 标记在局部编码分组中的失效节点
			lostNodeId := i
			lostChunksIndex = append(lostChunksIndex, lostNodeId)
			// 临时声明的数组游标，适配二维数组 LOCAL_CHUNK_ROUTE_TABLE 使用
			iter := 0
			// 逐组计数检查数据块状态
			for xorCodeGroupIndex := range LOCAL_CHUNK_ROUTE_TABLE {
				curGroup := LOCAL_CHUNK_ROUTE_TABLE[xorCodeGroupIndex]
				// Note：长度减 1，因为 LOCAL_CHUNK_ROUTE_TABLE 的每行留了最后一个位置给了局部编码块
				iterOffset := len(curGroup) - 1
				iter = iter + iterOffset
				if lostNodeId < iter {
					// 每个局部编码组只支持一个块的丢失，用 map 来查重
					_, ok := lostLocalChunksMap[xorCodeGroupIndex]
					if !ok {
						// 通过了查重检查，还需要经过局部校验块检查
						localChunkRoute := curGroup[len(curGroup)-1]
						localChunkPath := CHUNKS_STORE_PATH + filename + "-" + localChunkRoute.Nodeid
						_, err := os.Stat(localChunkPath)
						if err != nil {
							repairSchema = "global"
						} else {
							lostLocalChunksMap[xorCodeGroupIndex] = lostNodeId
						}
					} else {
						// 一旦发现存在局部编码组的失效数据块数量大于 2 ，只能换全局编码修复数据
						repairSchema = "global"
					}
					break
				}
			}
		} else {
			chunkSize = chunkInfo.Size()
		}
	}

	// 如果发生了节点失效情况，则采取重构读（修复块）措施
	if len(lostChunksIndex) > 0 {
		if len(lostChunksIndex) > L {
			// 判断失效块数量是否超过局部编码组数量，并调整修复模式
			repairSchema = "global"
		}
		// 根据标记确定恢复方式
		switch repairSchema {
		case "local":
			repairLocalGroup(dataChunkPaths, lostLocalChunksMap, uint64(chunkSize))
		default:
			repairWithRSCode(dataChunkPaths, lostChunksIndex, K, M)
		}
	}

	// 创建拼接后的编码块组的文件句柄
	cachePath = CACHE_STORE_PATH + filename
	// 在新一轮循环中（此时确认已经完成了数据块修复）实施块拼接，生成文件分片
	cacheChunk, err := os.OpenFile(cachePath, os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		log.Println("os.OpenFile Error: ", err)
	}
	defer cacheChunk.Close()
	// 拼接逻辑有问题，应该调用 reedsolomon join 方法拼接
	for i := 0; i < K; i++ {
		os.Open(dataChunkPaths[i])
	}
	RSEncoder, err := reedsolomon.NewStream(K, M)
	if err != nil {
		log.Println("reedsolomon.NewStream Error: ", err)
	}
	var dataChunkReader []io.Reader = make([]io.Reader, K)
	for i := 0; i < K; i++ {
		dataChunkHandle, err := os.Open(dataChunkPaths[i])
		log.Println("os.Open Error: ", err)
		dataChunkReader[i] = dataChunkHandle
	}
	err = RSEncoder.Join(cacheChunk, dataChunkReader, fileSize)
	if err != nil {
		log.Println("RSEncoder.Join Error: ", err)
	}
	ctx.SendFile(cachePath, filename)
}

func ClearCache(ctx iris.Context) {
	// 定位到 /cache 目录下
	files, err := ioutil.ReadDir(CACHE_STORE_PATH)
	if err != nil {
		log.Println("ioutil.ReadDir Error: ", err)
		ctx.Writef("Clear Task Error!\n")
		return
	} else {
		for i := range files {
			f := files[i]
			if !f.IsDir() {
				err = os.Remove(CACHE_STORE_PATH + f.Name())
				if err != nil {
					log.Println("os.Remove Error: ", f.Name(), err)
				}
			}
		}
		ctx.Writef("Clear Task Finished!\n")
		return
	}
}

// 局部编码函数，N个块按位取异或
// Note：局部编码只对存编码块的目录序号（路径中的 nodeIndex ）敏感，与编码块的序号无关
func xorEncode(dataChunkPath []string, chunkSize uint64) {
	// 声明校验块哈希数组
	// 从参数中提供的块路径信息中提取文件名和编码块组序号，用于局部校验块的命名
	pathTemplate := strings.FieldsFunc(dataChunkPath[0], checkSplitRuneBy47)
	tmpChunkNameTemplate := strings.FieldsFunc(pathTemplate[len(pathTemplate)-1], checkSplitRuneBy45)

	// 按路由表进行局部分组编码
	// 声明两个变量作为分组的游标，这里的 dataChunkPath 是一维数组，所以需要分截成二维数组来处理
	iter_b := 0
	iter_e := 0
	for i := range LOCAL_CHUNK_ROUTE_TABLE {
		route := LOCAL_CHUNK_ROUTE_TABLE[i]
		// Note：长度减 1，因为 LOCAL_CHUNK_ROUTE_TABLE 的每行留了最后一个位置给了局部编码块
		iter_e = iter_b + len(route) - 1
		xorCodeGroup := dataChunkPath[iter_b:iter_e]
		chunkBuff := make([]byte, chunkSize)
		encodeBuff := make([]byte, chunkSize)
		// 逐个读取局部组每个块到缓存
		for j := range xorCodeGroup {
			chunkHandle, _ := os.OpenFile(xorCodeGroup[j], os.O_RDONLY, 0666)
			chunkHandle.Read(chunkBuff)
			chunkHandle.Close()
			encodeBuff = xorOps4Bytes(encodeBuff, chunkBuff)
		}
		// 保存局部校验块
		nodeIndex := route[len(route)-1].Nodeid
		tmpChunkNameTemplate[len(tmpChunkNameTemplate)-1] = nodeIndex
		chunkName := strings.Join(tmpChunkNameTemplate, `-`)
		pathTemplate[len(pathTemplate)-1] = chunkName
		localChunkPath := strings.Join(pathTemplate, `/`)
		localChunkHandle, err := os.OpenFile(localChunkPath, os.O_WRONLY|os.O_CREATE, 0666)
		if err != nil {
			log.Println("xorEncode() => Open Local Chunk Error: ", err)
		}
		defer localChunkHandle.Close()
		localChunkHandle.Write(encodeBuff)
		// 移动数组光标
		iter_b = iter_e
	}
}

// 按组进行局部修复
// 传入参数：数据块路径序列
func repairLocalGroup(dataChunkPaths []string, lostLocalChunksMap map[int]int, chunkSize uint64) {
	log.Println(">>> repairLocalGroup")
	// 提取块名称的模板
	pathTemplate := strings.FieldsFunc(dataChunkPaths[0], checkSplitRuneBy47)
	tmpChunkNameTemplate := strings.FieldsFunc(pathTemplate[len(pathTemplate)-1], checkSplitRuneBy45)
	// 按局部编码组进行修复，循环是因为可能存在多个组同时需要修复
	// key：失效块所在的组编号，value：失效节点的序号
	for xorCodeGroupIndex, lostNodeIndex := range lostLocalChunksMap {
		// 声明失效块
		targetChunkPath := dataChunkPaths[lostNodeIndex]
		targetChunk, err := os.OpenFile(targetChunkPath, os.O_WRONLY|os.O_CREATE, 0666)
		if err != nil {
			log.Println("repairLocalGroup() => Open Lost Chunk Error: ", err)
		}
		targetBytes := make([]byte, chunkSize)
		chunkBuff := make([]byte, chunkSize)
		// 循环修复组内失效块
		curGroup := LOCAL_CHUNK_ROUTE_TABLE[xorCodeGroupIndex]
		for i := range curGroup {
			// 查找有效块进行异或运算
			if curGroup[i].Nodeid != strconv.Itoa(lostNodeIndex) {
				// 获取这个文件
				tmpChunkNameTemplate[len(tmpChunkNameTemplate)-1] = curGroup[i].Nodeid
				chunkName := strings.Join(tmpChunkNameTemplate, `-`)
				pathTemplate[len(pathTemplate)-1] = chunkName
				localChunkPath := strings.Join(pathTemplate, `/`)
				localChunkHandle, err := os.Open(localChunkPath)
				if err != nil {
					log.Println("repairLocalGroup() => Open Local Chunk Error: ", err)
				}
				localChunkHandle.Read(chunkBuff)
				targetBytes = xorOps4Bytes(targetBytes, chunkBuff)
			}
		}
		// 写入异或结果到目表块文件，至此失效块的恢复过程完成
		targetChunk.Write(targetBytes)
	}
}

// 按组进行全局修复，使用 RS 编码方案
func repairWithRSCode(dataChunkPaths []string, lostChunks []int, k int, m int) {
	log.Println(">>> repairWithRSCode")
	// 声明有效编码块的序列，作为 RS 码重构函数入参之一，失效块的对应位置留空 nil
	var chunksReaders []io.Reader
	// 按照顺序加载有效数据块
	for i := range dataChunkPaths {
		dataChunkHandle, err := os.Open(dataChunkPaths[i])
		if err != nil {
			chunksReaders = append(chunksReaders, nil)
		} else {
			var dataReader io.Reader = nil
			dataReader = dataChunkHandle
			chunksReaders = append(chunksReaders, dataReader)
		}
	}
	// 提取编码块路径模板
	pathTemplate := strings.FieldsFunc(dataChunkPaths[0], checkSplitRuneBy47)
	tmpChunkNameTemplate := strings.FieldsFunc(pathTemplate[len(pathTemplate)-1], checkSplitRuneBy45)
	// 按顺序加载有效全局校验块
	for i := 0; i < m; i++ {
		index := strconv.Itoa(k + i)
		tmpChunkNameTemplate[len(tmpChunkNameTemplate)-1] = index
		chunkName := strings.Join(tmpChunkNameTemplate, `-`)
		pathTemplate[len(pathTemplate)-1] = chunkName
		// Note：在前面切割字符串的时候把头部的 / 去掉了，因为是绝对路径，所以这里要补回来
		pariChunkPath := strings.Join(pathTemplate, `/`)
		pariChunkHandle, err := os.Open(pariChunkPath)
		if err != nil {
			log.Println("repairWithRSCode() => Open Parity Chunk Error: ", err)
			chunksReaders = append(chunksReaders, nil)
			continue
		} else {
			var pariReader io.Reader = nil
			pariReader = pariChunkHandle
			chunksReaders = append(chunksReaders, pariReader)
		}
	}

	// 声明新数据块的接收器，作为 RS 码重构函数入参之一，在需要还原的对应位置设置有效的 io.Writer 对象
	repairShards := make([]io.Writer, k+m)
	for i := range lostChunks {
		index := lostChunks[i]
		var err error
		repairShards[index], err = os.Create(dataChunkPaths[index])
		if err != nil {
			log.Println("repairWithRSCode() => Open Data Chunk Error: ", err)
		}
	}
	RSEncoder, _ := reedsolomon.NewStream(K, M)
	err := RSEncoder.Reconstruct(chunksReaders, repairShards)
	if err != nil {
		log.Println("RS Encoder Reconstruct Error: ", err)
	}
}
